#include "PolyVoxGenerator.h"
#include "MaterialDictionary.h"
#include "PlantDictionary.h"
#include "Settings.h"

//#include <PolyVoxCore/LargeVolume.h>
#include <PolyVoxCore/MaterialDensityPair.h>
#include <PolyVoxCore/ConstVolumeProxy.h>
#include <PolyVoxCore/SurfaceExtractor.h>
#include <PolyVoxCore/MeshDecimator.h>
#include <PolyVoxCore/AmbientOcclusionCalculator.h>

#include <OgreRoot.h>
#include <OgreSceneManager.h>
#include <OgreManualObject.h>

#include <PagedGeometry.h>
#include <TreeLoader3D.h>
#include <BatchPage.h>
#include <ImpostorPage.h>

#include <boost/filesystem.hpp>

#include <iostream>
#include <fstream>
#include <sstream>

PolyVoxGenerator::PolyVoxGenerator() 
    : GeometryGenerator(), 
    volume(polyvox_bind(&PolyVoxGenerator::dataRequiredHandler,this,polyvox_placeholder_1,polyvox_placeholder_2), polyvox_bind(&PolyVoxGenerator::dataOverflowHandler,this,polyvox_placeholder_1,polyvox_placeholder_2)), 
    cameraPosition(Ogre::Vector3::ZERO), highestPoint(0.0), finished(false)
{
    volume.setMaxNumberOfBlocksInMemory(32768);
    smgr = Ogre::Root::getSingletonPtr()->createSceneManager("DefaultSceneManager");
    Settings *settings = Settings::getSingletonPtr();
    x_scale = settings->getSetting<double>("PolyVoxGenerator.XScale");
    y_scale = settings->getSetting<double>("PolyVoxGenerator.YScale");
    z_scale = settings->getSetting<double>("PolyVoxGenerator.ZScale");
   
    page_x_size = settings->getSetting<int>("PolyVoxGenerator.PageSizeX");
    page_y_size = settings->getSetting<int>("PolyVoxGenerator.PageSizeY");
    page_z_size = settings->getSetting<int>("PolyVoxGenerator.PageSizeZ");

    page_x_dist = settings->getSetting<int>("PolyVoxGenerator.PageDistanceX");
    page_y_dist = settings->getSetting<int>("PolyVoxGenerator.PageDistanceY");
    page_z_dist = settings->getSetting<int>("PolyVoxGenerator.PageDistanceZ");

    useFaceNormals = settings->getSetting<bool>("PolyVoxGenerator.UseFaceNormals");
    decimateMesh = settings->getSetting<bool>("PolyVoxGenerator.DecimateMeshes");
    decimationFactor = settings->getSetting<float>("PolyVoxGenerator.DecimationFactor");
}

PolyVoxGenerator::~PolyVoxGenerator()
{
    finished = true;
    generationThread.interrupt();
    generationThread.join();
    pagingThread.interrupt();
    pagingThread.join();
    boost::filesystem::remove_all(boost::filesystem::path("pages"));
    boost::filesystem::create_directory("pages");
}

void PolyVoxGenerator::setMapSize(uint32_t x, uint32_t y, uint32_t z)
{
    map_x_size = x;
    map_y_size = y;
    map_z_size = z;
}

void PolyVoxGenerator::setTile(uint32_t x, uint32_t y, uint32_t z, TileType tile_type, TileMaterialType tile_material, uint32_t material_index, uint16_t material_type)
{
    DFVoxel v;
    bool hasTerrain = false;

    material_index += NUM_DEFAULT_MATERIALS;

    if (material_type == 420) material_type = 1;

    if (material_type == 1) material_index += materialDictionary->getNumInorganics();
    switch (tile_material)
    {
    case TileMaterialType::CONSTRUCTION:
        material_index |= CONSTRUCTED_MASK;
        break;
    case TileMaterialType::GRASS_LIGHT:
    case TileMaterialType::GRASS_DARK:
    case TileMaterialType::GRASS_DRY:
    case TileMaterialType::GRASS_DEAD:
        material_index = (uint16_t)TextureIndex::TEXTURE_GRASS;
        break;
    case TileMaterialType::FROZEN_LIQUID:
        material_index = (uint16_t)TextureIndex::TEXTURE_ICE;
        break;
    default:
        break;
    }

    switch(tile_type)
    {
    case TileType::SAPLING:
    case TileType::SHRUB:
        material_index = (uint16_t)TextureIndex::TEXTURE_GRASS;
    case TileType::FLOOR:
    case TileType::BOULDER:
    case TileType::BROOK_BED:
    case TileType::PEBBLES:
        v = volume.getVoxelAt(x,y,z*8);
        v.setDensity(15);
        v.setMaterial(material_index);
        volume.setVoxelAt(x,y,z*8,v);
        hasTerrain = true;
        break;
    case TileType::TREE:
        material_index = (uint16_t)TextureIndex::TEXTURE_GRASS;
        for (int i=0; i < 2; i++)
        {
            v = volume.getVoxelAt(x,y,z*8+i);
            v.setDensity(15);
            v.setMaterial(material_index);
            volume.setVoxelAt(x,y,z*8+i,v);
        }
        hasTerrain = true;
        break;
    case TileType::WALL:
    case TileType::FORTIFICATION:
        for (int i=0; i < 8; i++)
        {
            v = volume.getVoxelAt(x,y,z*8+i);
            v.setDensity(15);
            v.setMaterial(material_index);
            volume.setVoxelAt(x,y,z*8+i,v);
        }
        hasTerrain = true;
        break;
    case TileType::RAMP:
        for (int i=0; i < 8; i++)
        {
            v = volume.getVoxelAt(x,y,z*8+i);
            v.setDensity(15-(i*1.5));
            v.setMaterial(material_index);
            volume.setVoxelAt(x,y,z*8+i,v);
        }
        hasTerrain = true;
        break;
    default:
        break;
    }
    if (hasTerrain && (z+1)*8 > highestPoint) highestPoint = (z+1)*8;
}

void PolyVoxGenerator::addLiquid(uint32_t x, uint32_t y, uint32_t z, LiquidType liquid_type, uint16_t flow_size)
{
    int i = 1;
    if (volume.getVoxelAt(x,y,z*8).getDensity()==0 && volume.getVoxelAt(x,y,z*8-1).getDensity() > 0) i = 0;
    for (; i <= flow_size; i++)
    {
        DFVoxel v = volume.getVoxelAt(x,y,z*8+i);
        v.setDensity(15);
        if (liquid_type == LiquidType::WATER) v.setMaterial(TextureIndex::TEXTURE_WATER);
        else v.setMaterial(TextureIndex::TEXTURE_MAGMA);
        volume.setVoxelAt(x,y,z*8+i,v);
    }
}

void PolyVoxGenerator::addTree(uint32_t x, uint32_t y, uint32_t z, uint32_t material, bool is_shrub)
{
    Plant p;
    p.name = materialDictionary->getName(material, 1);
    p.position = Ogre::Vector3(x*x_scale,z*z_scale*8,-(Ogre::Real)(y*y_scale));
    p.is_shrub = is_shrub;
    plants.push_back(p);
}

void PolyVoxGenerator::setCamera(Ogre::Camera *c)
{
    cam = c;
    trees->setCamera(c);
}

void PolyVoxGenerator::notifyCameraPosition(Ogre::Vector3 camPos)
{
    boost::mutex::scoped_lock lock(cameraPositionMutex);
    cameraPosition = Ogre::Vector3(camPos.x / x_scale, -camPos.z / y_scale, camPos.y / z_scale);
}

void PolyVoxGenerator::pageSelectionThread()
{
    try
    {
        while (true)
        {
            boost::this_thread::interruption_point();
            //boost::this_thread::sleep(boost::posix_time::milliseconds(5));

            cameraPositionMutex.lock();
            Ogre::Real minX = (int)cameraPosition.x - page_x_size*page_x_dist;
            Ogre::Real maxX = (int)cameraPosition.x + page_x_size*page_x_dist;
            Ogre::Real minY = (int)cameraPosition.y - page_y_size*page_y_dist;
            Ogre::Real maxY = (int)cameraPosition.y + page_y_size*page_y_dist;
            Ogre::Real minZ = (int)cameraPosition.z - page_z_size*page_z_dist;
            Ogre::Real maxZ = (int)cameraPosition.z + page_z_size*page_z_dist;
            cameraPositionMutex.unlock();

            /*minX = (Ogre::Real)((int)minX & -page_x_size);
            maxX = (Ogre::Real)((int)maxX & -page_x_size);
            minY = (Ogre::Real)((int)minY & -page_y_size);
            maxY = (Ogre::Real)((int)maxY & -page_y_size);
            minZ = (Ogre::Real)((int)minZ & -page_z_size);
            maxZ = (Ogre::Real)((int)maxZ & -page_z_size);*/

            minX -= Ogre::Math::Sign(minX)*((int)Ogre::Math::Abs(minX)%page_x_size);
            maxX -= Ogre::Math::Sign(maxX)*((int)Ogre::Math::Abs(maxX)%page_x_size);
            minY -= Ogre::Math::Sign(minY)*((int)Ogre::Math::Abs(minY)%page_y_size);
            maxY -= Ogre::Math::Sign(maxY)*((int)Ogre::Math::Abs(maxY)%page_y_size);
            minZ -= Ogre::Math::Sign(minZ)*((int)Ogre::Math::Abs(minZ)%page_z_size);
            maxZ -= Ogre::Math::Sign(maxZ)*((int)Ogre::Math::Abs(maxZ)%page_z_size);

            for (Ogre::Real x = minX; x <= maxX; x+=page_x_size)
            {
                for (Ogre::Real y = minY; y <= maxY; y+=page_y_size)
                {
                    for (Ogre::Real z = minZ; z <= maxZ; z+=page_z_size)
                    {
                        boost::this_thread::interruption_point();
                        if (x < -page_x_size || x > (map_x_size+1)*16*x_scale || y < -page_y_size || y > (map_y_size+1)*16*y_scale || z < -page_z_size || z > (highestPoint+1)*8) continue;

                        Ogre::Vector3 blockPos(x,y,z);
                        loadedPageMutex.lock();
                        if (loadedPages.find(blockPos)==loadedPages.end())
                        {
                            loadedPages[blockPos] = -1;
                            pageGenerationMutex.lock();
                            pageGenerationQueue.push(std::make_pair(Ogre::Vector3(cameraPosition.x,cameraPosition.y,cameraPosition.z/8.0).distance(Ogre::Vector3(x+page_x_size/2, y+page_y_size/2, z/8.0+page_z_size/16)),blockPos));
                            pageGenerationMutex.unlock();
                        }
                        else if (loadedPages[blockPos] != -1) loadedPages[blockPos] = 0;
                        loadedPageMutex.unlock();
                    }
                }
            }
        }
    }
    catch (boost::thread_interrupted)
    {
    }
}

void PolyVoxGenerator::pagePreparationThread()
{
    try
    {
        while (true)
        {
            boost::this_thread::interruption_point();
            //boost::this_thread::sleep(boost::posix_time::milliseconds(5));
            if (pageGenerationQueue.empty()) continue;
            pageGenerationMutex.lock();
            Ogre::Vector3 p = pageGenerationQueue.top().second;
            pageGenerationQueue.pop();
            pageGenerationMutex.unlock();
            //PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal> undecimatedMesh;
            PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal> *mesh = new PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal>();
            PolyVox::Region region(PolyVox::Vector3DInt32(p.x,p.y,p.z), PolyVox::Vector3DInt32(p.x+page_x_size, p.y+page_y_size, p.z+page_z_size));
            if (decimateMesh)
            {
                PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal> undecimatedMesh;
                PolyVox::SurfaceExtractor<PolyVox::LargeVolume,DFVoxel> surfaceExtractor(&volume,region, &undecimatedMesh);
                surfaceExtractor.execute();
                PolyVox::MeshDecimator<PolyVox::PositionMaterialNormal> decimator(&undecimatedMesh, mesh, decimationFactor);
                decimator.execute();
            }
            else
            {
                PolyVox::SurfaceExtractor<PolyVox::LargeVolume,DFVoxel> surfaceExtractor(&volume,region, mesh);
                surfaceExtractor.execute();
            }
            
            if (mesh->isEmpty()) continue;

            //PolyVox::Array<3, uint8_t> *ambient = 0;
            //ambient = new PolyVox::Array<3, uint8_t>(PolyVox::ArraySizes(page_x_size+1)(page_y_size+1)(page_z_size+1));
            //PolyVox::AmbientOcclusionCalculator<PolyVox::LargeVolume,DFVoxel> ambientCalculator(&volume, ambient,region,15.0f,50);

            //ambientCalculator.execute();

            MeshRenderInfo renderInfo = {p, mesh, 0};
            boost::mutex::scoped_lock rendlock(pageRenderMutex);
            pageRenderQueue.push(renderInfo);
        }
    }
    catch (boost::thread_interrupted)
    {
    }
}

Ogre::Vector3 PolyVoxGenerator::getInitialCameraPosition()
{
    return Ogre::Vector3(0,highestPoint*z_scale,0);
}

void PolyVoxGenerator::update(double timeSinceLastFrame)
{
    static int frameskip = 0;
    trees->update();
    if (frameskip++ % 3) return;
    pageRenderMutex.lock();
    if (!pageRenderQueue.empty())
    {
        drawPage(pageRenderQueue.front());
        delete pageRenderQueue.front().mesh;
        if (pageRenderQueue.front().ambient) delete pageRenderQueue.front().ambient;
        pageRenderQueue.pop();
    }
    pageRenderMutex.unlock();
    loadedPageMutex.lock();
    for (boost::unordered_map<Ogre::Vector3,double,Vector3Hash>::iterator it = loadedPages.begin(); it != loadedPages.end(); it++)
    {
        double pageTime = (*it).second;
        if (pageTime == -1) continue;
        pageTime += timeSinceLastFrame;
        (*it).second = pageTime;
        if (pageTime > 3.0) unloadPage((*it).first);
    }
    loadedPageMutex.unlock();
}

void PolyVoxGenerator::drawPage(MeshRenderInfo &renderInfo)
{
    Ogre::ManualObject *ogreMesh;
    std::ostringstream ss;
    const Ogre::Vector3 &v = renderInfo.position;
     PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal> *mesh = renderInfo.mesh;
    ss << "page" << v.x << "." << v.y << "." << v.z;
    ogreMesh = smgr->createManualObject(ss.str());
    ogreMesh->setDynamic(true);
    ss << ".node";
    //std::cout << "Creating Ogre node at " << x << "." << y << "." << z << std::endl;
    Ogre::SceneNode *node = ((Ogre::SceneNode*)smgr->getRootSceneNode()->getChild("Map"))->createChildSceneNode(ss.str(),Ogre::Vector3(x_scale*v.x,y_scale*v.y,z_scale*v.z));
    node->attachObject(ogreMesh);

    ogreMesh->begin("TriplanarTexturing", Ogre::RenderOperation::OT_TRIANGLE_LIST);
    {
        const std::vector<PolyVox::PositionMaterialNormal>& vecVertices = mesh->getVertices();
        const std::vector<uint32_t>& vecIndices = mesh->getIndices();
        ogreMesh->estimateIndexCount(vecIndices.size());
        ogreMesh->estimateVertexCount(vecVertices.size());
        unsigned int uLodLevel = 0;
        int beginIndex = mesh->m_vecLodRecords[uLodLevel].beginIndex;
        int endIndex = mesh->m_vecLodRecords[uLodLevel].endIndex;
        for(int index = beginIndex; index < endIndex; index+=3)
        {
            const PolyVox::PositionMaterialNormal& v1 = vecVertices[vecIndices[index]];
            const PolyVox::PositionMaterialNormal& v2 = vecVertices[vecIndices[index+1]];
            const PolyVox::PositionMaterialNormal& v3 = vecVertices[vecIndices[index+2]];

            PolyVox::Vector3DFloat scaleVector(x_scale,y_scale,z_scale);

            PolyVox::Vector3DFloat v1pos = v1.getPosition()*scaleVector;
            PolyVox::Vector3DFloat v2pos = v2.getPosition()*scaleVector;
            PolyVox::Vector3DFloat v3pos = v3.getPosition()*scaleVector;

            Ogre::ColourValue v1col, v2col, v3col;
            Ogre::Real v1tex, v2tex, v3tex;

            decodeMaterial(v1.getMaterial(), &v1col, &v1tex);
            decodeMaterial(v2.getMaterial(), &v2col, &v2tex);
            decodeMaterial(v3.getMaterial(), &v3col, &v3tex);

            PolyVox::Vector3DFloat v1norm = v1.getNormal();
            PolyVox::Vector3DFloat v2norm = v2.getNormal();
            PolyVox::Vector3DFloat v3norm = v3.getNormal();
            v1norm /= scaleVector;
            v2norm /= scaleVector;
            v3norm /= scaleVector;
            v1norm.normalise();
            v2norm.normalise();
            v3norm.normalise();

            PolyVox::Vector3DFloat normal = (v1pos-v2pos).cross(v2pos-v3pos);
            normal.normalise();

            if (v1norm == PolyVox::Vector3DFloat(0,0,0) || useFaceNormals) v1norm = normal;
            if (v2norm == PolyVox::Vector3DFloat(0,0,0) || useFaceNormals) v2norm = normal;
            if (v3norm == PolyVox::Vector3DFloat(0,0,0) || useFaceNormals) v3norm = normal;

            if (v1tex == v2tex && v2tex == v3tex)
            {
                drawVertex(ogreMesh, v1pos, v1norm, v1col, v1tex);
                drawVertex(ogreMesh, v2pos, v2norm, v2col, v1tex);
                drawVertex(ogreMesh, v3pos, v3norm, v3col, v1tex);
            }
            else
            {
                PolyVox::Vector3DFloat v13pos = v1pos+v3pos;
                PolyVox::Vector3DFloat v12pos = v1pos+v2pos;
                PolyVox::Vector3DFloat v23pos = v2pos+v3pos;
                v12pos /= 2;
                v13pos /= 2;
                v23pos /= 2;

                PolyVox::Vector3DFloat v12norm = v1norm+v2norm;
                PolyVox::Vector3DFloat v13norm = v1norm+v3norm;
                PolyVox::Vector3DFloat v23norm = v2norm+v3norm;
                v12norm.normalise();
                v13norm.normalise();
                v23norm.normalise();

                if (v1tex == v2tex)
                {
                    drawVertex(ogreMesh, v1pos, v1norm, v1col, v1tex);
                    drawVertex(ogreMesh, v2pos, v2norm, v2col, v2tex);
                    drawVertex(ogreMesh, v23pos, v23norm, v2col, v2tex);

                    drawVertex(ogreMesh, v1pos, v1norm, v1col, v1tex);
                    drawVertex(ogreMesh, v23pos, v23norm, v2col, v2tex);
                    drawVertex(ogreMesh, v13pos, v13norm, v1col, v1tex);

                    drawVertex(ogreMesh, v3pos, v3norm, v3col, v3tex);
                    drawVertex(ogreMesh, v13pos, v13norm, v3col, v3tex);
                    drawVertex(ogreMesh, v23pos, v23norm, v3col, v3tex);
                }
                else if (v1tex == v3tex)
                {
                    drawVertex(ogreMesh, v3pos, v3norm, v3col, v3tex);
                    drawVertex(ogreMesh, v1pos, v1norm, v1col, v1tex);
                    drawVertex(ogreMesh, v12pos, v12norm, v1col, v1tex);

                    drawVertex(ogreMesh, v3pos, v3norm, v3col, v3tex);
                    drawVertex(ogreMesh, v12pos, v12norm, v1col, v1tex);
                    drawVertex(ogreMesh, v23pos, v23norm, v3col, v3tex);

                    drawVertex(ogreMesh, v2pos, v2norm, v2col, v2tex);
                    drawVertex(ogreMesh, v23pos, v23norm, v2col, v2tex);
                    drawVertex(ogreMesh, v12pos, v12norm, v2col, v2tex);
                }
                else if (v2tex == v3tex)
                {
                    drawVertex(ogreMesh, v2pos, v2norm, v2col, v2tex);
                    drawVertex(ogreMesh, v3pos, v3norm, v3col, v3tex);
                    drawVertex(ogreMesh, v13pos, v13norm, v3col, v3tex);

                    drawVertex(ogreMesh, v2pos, v2norm, v2col, v2tex);
                    drawVertex(ogreMesh, v13pos, v13norm, v3col, v3tex);
                    drawVertex(ogreMesh, v12pos, v12norm, v2col, v2tex);

                    drawVertex(ogreMesh, v1pos, v1norm, v1col, v1tex);
                    drawVertex(ogreMesh, v12pos, v12norm, v1col, v1tex);
                    drawVertex(ogreMesh, v13pos, v13norm, v1col, v1tex);
                }
                else
                {
                    PolyVox::Vector3DFloat v4pos = v1pos+v2pos+v3pos;
                    v4pos /= 3;
                    PolyVox::Vector3DFloat v4norm = v1norm+v2norm+v3norm;
                    v4norm.normalise();

                    drawVertex(ogreMesh, v1pos, v1norm, v1col, v1tex);
                    drawVertex(ogreMesh, v12pos, v12norm, v1col, v1tex);
                    drawVertex(ogreMesh, v4pos, v4norm, v1col, v1tex);

                    drawVertex(ogreMesh, v1pos, v1norm, v1col, v1tex);
                    drawVertex(ogreMesh, v4pos, v4norm, v1col, v1tex);
                    drawVertex(ogreMesh, v13pos, v13norm, v1col, v1tex);

                    drawVertex(ogreMesh, v3pos, v3norm, v3col, v3tex);
                    drawVertex(ogreMesh, v13pos, v13norm, v3col, v3tex);
                    drawVertex(ogreMesh, v4pos, v4norm, v3col, v3tex);

                    drawVertex(ogreMesh, v3pos, v3norm, v3col, v3tex);
                    drawVertex(ogreMesh, v4pos, v4norm, v3col, v3tex);
                    drawVertex(ogreMesh, v23pos, v23norm, v3col, v3tex);

                    drawVertex(ogreMesh, v2pos, v2norm, v2col, v2tex);
                    drawVertex(ogreMesh, v23pos, v23norm, v2col, v2tex);
                    drawVertex(ogreMesh, v4pos, v4norm, v2col, v2tex);

                    drawVertex(ogreMesh, v2pos, v2norm, v2col, v2tex);
                    drawVertex(ogreMesh, v4pos, v4norm, v2col, v2tex);
                    drawVertex(ogreMesh, v12pos, v12norm, v2col, v2tex);
                }
            }
        }
    }
    ogreMesh->end();
    loadedPageMutex.lock();
    loadedPages[v] = 0;
    loadedPageMutex.unlock();
}

void PolyVoxGenerator::drawVertex(Ogre::ManualObject *mesh, const PolyVox::Vector3DFloat &position, const PolyVox::Vector3DFloat &normal, const Ogre::ColourValue &colour, const Ogre::Real &texture)
{
    mesh->position(position.getX(), position.getY(), position.getZ());
    mesh->normal(normal.getX(), normal.getY(), normal.getZ());
    mesh->colour(colour);
    mesh->textureCoord(texture);
}

void PolyVoxGenerator::decodeMaterial(uint16_t index, Ogre::ColourValue *colour, Ogre::Real *texture)
{
    bool constructed = false;
    *colour = Ogre::ColourValue::White;
    *texture = 0;
    if (index < NUM_DEFAULT_MATERIALS) *texture = (Ogre::Real)index;
    else
    {
        if (index & CONSTRUCTED_MASK)
        {
            index ^= CONSTRUCTED_MASK;
            constructed = true;
        }
        if (index-NUM_DEFAULT_MATERIALS >= materialDictionary->getNumInorganics())
        {
            *colour = materialDictionary->getColour(index-NUM_DEFAULT_MATERIALS-materialDictionary->getNumInorganics(),1);
            *texture = (Ogre::Real)TextureIndex::TEXTURE_WOOD;
        }
        else
        {
            *colour = materialDictionary->getColour(index-NUM_DEFAULT_MATERIALS,0);
            if (constructed) *texture = (Ogre::Real)TextureIndex::TEXTURE_CONSTRUCTED;
            else *texture = (Ogre::Real)TextureIndex::TEXTURE_ROUGH;
        }
    }
}

void PolyVoxGenerator::unloadPage(Ogre::Vector3 v)
{
    std::ostringstream ss;
    ss << "page" << v.x << "." << v.y << "." << v.z;
    if (smgr->hasManualObject(ss.str()))
    {
        std::string meshName = ss.str();
        ss << ".node";
        //std::cout << "Destroying Ogre node at " << v.x << "." << v.y << "." << v.z << std::endl;
        Ogre::SceneNode *n = (Ogre::SceneNode*)(smgr->getRootSceneNode()->getChild("Map")->removeChild(ss.str()));
        smgr->destroySceneNode(n);
        smgr->destroyManualObject(meshName);
        loadedPages.erase(v);
    }
}

void PolyVoxGenerator::prepareScene()
{
    Settings *settings = Settings::getSingletonPtr();
    cameraPosition = getInitialCameraPosition();

    smgr->getRootSceneNode()->createChildSceneNode("Map")->rotate(Ogre::Vector3::UNIT_X,Ogre::Degree(-90));

    generationThread = boost::thread(&PolyVoxGenerator::pagePreparationThread,this);
    pagingThread = boost::thread(&PolyVoxGenerator::pageSelectionThread,this);
    
    trees = new Forests::PagedGeometry();
    trees->setCamera(smgr->createCamera("OverseerCam"));
    trees->setPageSize(80);
    trees->setInfinite();

    double plantBatchDistance = settings->getSetting<double>("General.PlantBatchDistance");
    double plantImpostorDistance = settings->getSetting<double>("General.PlantImpostorDistance");

    trees->addDetailLevel<Forests::BatchPage>(plantBatchDistance,plantImpostorDistance-plantBatchDistance);
    trees->addDetailLevel<Forests::ImpostorPage>(plantImpostorDistance,50);
    treeloader = new Forests::TreeLoader3D(trees,Forests::TBounds(0,-(map_y_size+1)*16*y_scale,(map_x_size+1)*16*x_scale,0));
    treeloader->setMinimumScale(0.1);
    treeloader->setMaximumScale(15.0);
    trees->setTempDir("impostors/");

    for (std::vector<Plant>::iterator it = plants.begin(); it != plants.end(); it++)
    {
        const Plant &p = (*it);
        Ogre::Entity *e;

        PlantModel plantModel;

        if (!p.is_shrub) plantModel = plantDictionary->getTreeModel(p.name);
        else plantModel = plantDictionary->getShrubModel(p.name);

        if (smgr->hasEntity(p.name)) e = smgr->getEntity(p.name);
        else e = smgr->createEntity(p.name,plantModel.name);

        Ogre::Degree yaw = Ogre::Degree(Ogre::Math::RangeRandom(0,360));
        Ogre::Real scale = Ogre::Math::RangeRandom(plantModel.minScale,plantModel.maxScale);
        treeloader->addTree(e,p.position,yaw,scale);
    }
    trees->setPageLoader(treeloader);

    plants.clear();
}

void PolyVoxGenerator::dataRequiredHandler(const PolyVox::ConstVolumeProxy<DFVoxel>& v, const PolyVox::Region& r)
{
    if (finished) return;
    const PolyVox::Vector3DInt32 &lowerCorner = r.getLowerCorner();
    const PolyVox::Vector3DInt32 &upperCorner = r.getUpperCorner();
    std::ostringstream ss;
    ss << "pages/" << lowerCorner.getX() << "." << lowerCorner.getY() << "." << lowerCorner.getZ() << ".page";
    std::ifstream page(ss.str().c_str(), std::ios::in | std::ios::binary);
    if (!page.is_open()) return;
    for (int x = lowerCorner.getX(); x <= upperCorner.getX(); x++)
    {
        for (int y = lowerCorner.getY(); y <= upperCorner.getY(); y++)
        {
            for (int z = lowerCorner.getZ(); z <= upperCorner.getZ(); z++)
            {
                DFVoxel voxel = v.getVoxelAt(x,y,z);
                uint16_t value = 0;
                page.read(reinterpret_cast<char*>(&value),2);
                voxel.setDensity(value&0xf);
                voxel.setMaterial(value>>4);
                v.setVoxelAt(x,y,z,voxel);
            }
        }
    }
    page.close();
    boost::filesystem::remove(boost::filesystem::path(ss.str()));
}

void PolyVoxGenerator::dataOverflowHandler(const PolyVox::ConstVolumeProxy<DFVoxel>& v, const PolyVox::Region& r)
{
    if (finished) return;
    const PolyVox::Vector3DInt32 &lowerCorner = r.getLowerCorner();
    const PolyVox::Vector3DInt32 &upperCorner = r.getUpperCorner();
    std::ostringstream ss;
    ss << "pages/" << lowerCorner.getX() << "." << lowerCorner.getY() << "." << lowerCorner.getZ() << ".page";
    std::ofstream page(ss.str().c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
    if (!page.is_open()) return;
    for (int x = lowerCorner.getX(); x <= upperCorner.getX(); x++)
    {
        for (int y = lowerCorner.getY(); y <= upperCorner.getY(); y++)
        {
            for (int z = lowerCorner.getZ(); z <= upperCorner.getZ(); z++)
            {
                const DFVoxel &voxel = v.getVoxelAt(x,y,z);
                uint16_t value = voxel.getMaterial() << 4 | voxel.getDensity();
                page.write(reinterpret_cast<char*>(&value),2);
            }
        }
    }
    page.close();
}
